package logyt

import (
	"fmt"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"strings"
)

// encoder

type LevelStyle string

// 以后可以自定义更多Level编码器
const (
	Lowercase      LevelStyle = "lowercase"
	Capital        LevelStyle = "capital"
	LowercaseColor LevelStyle = "lowercase_color"
	CapitalColor   LevelStyle = "capital_color"
)

// CallerStyle 以后可以自定义更多Caller编码器
type CallerStyle string

const (
	Short CallerStyle = "short"
	Full  CallerStyle = "full"
)

// LoggerNameStyle 以后可以自定义更多name编码器
type LoggerNameStyle string

const (
	FullName LoggerNameStyle = "full_name"
)

const defaultLogName = "logyt"

type TimeStyle string

const (
	EpochTime       TimeStyle = "epoch_time"
	EpochMillisTime TimeStyle = "epoch_time_millis"
	EpochNanosTime  TimeStyle = "epoch_time_nanos"
	ISO8601         TimeStyle = "iso_8601"
	RFC3339         TimeStyle = "efc_3339"
	RFC3339Nano     TimeStyle = "rfc_3339_nano"
	Layout          TimeStyle = "layout_time"
)

type DurationStyle string

const (
	SecondsDuration DurationStyle = "seconds_duration"
	NanosDuration   DurationStyle = "nano_duration"
	MillisDuration  DurationStyle = "millis_duration"
	StringDuration  DurationStyle = "string_duration"
)

// 后续还可以支持各个字段的颜色输出
var (
	isTerm          = true
	outStyle        = PlainText
	timeKey         = ""
	levelKey        = ""
	nameKey         = ""
	callerKey       = ""
	functionKey     = ""
	messageKey      = ""
	stacktraceKey   = ""
	lineEnding      = "\n"
	levelStyle      = Lowercase
	timeStyle       = ISO8601
	timeFormat      = "2006-01-02T15:04:05.000Z0700" //支持自定义的时间格式化编码器TimeEncoderOfLayout
	callerStyle     = Short
	loggerNameStyle = FullName
	durationStyle   = SecondsDuration
)

type OutStyle string

const (
	PlainText OutStyle = "plain_text"
	Json      OutStyle = "json"
)

// write sync
var (
	outputPaths = []string{"stdout"}
)

//core

var (
	logLevel = zap.NewAtomicLevelAt(zapcore.InfoLevel)
)

type LogLevel uint32

const (
	DebugLevel LogLevel = iota
	InfoLevel
	WarnLevel
	ErrorLevel
	DPanicLevel
	PanicLevel
	FatalLevel
	NoneLevel
)

func logLevelToZap(l LogLevel) zapcore.Level {
	switch l {
	case DebugLevel:
		return zapcore.DebugLevel
	case InfoLevel:
		return zapcore.InfoLevel
	case WarnLevel:
		return zapcore.WarnLevel
	case ErrorLevel:
		return zapcore.ErrorLevel
	case DPanicLevel:
		return zapcore.DPanicLevel
	case PanicLevel:
		return zapcore.PanicLevel
	case FatalLevel:
		return zapcore.FatalLevel
	case NoneLevel:
		return zapcore.FatalLevel + 1
	}
	return zapcore.InfoLevel
}
func SetLogLevel(l LogLevel) {
	logLevel.SetLevel(logLevelToZap(l))
}

// logger
var (
	addCaller  = false
	onFatal    = Exit
	callerSkip = 0
	name       = defaultLogName //日志名称
)

type OnFatalType string

const (
	GoExit OnFatalType = "go_exit"
	Panic  OnFatalType = "panic"
	Exit   OnFatalType = "exit"
)

var (
	stackLevel = zap.NewAtomicLevelAt(zapcore.ErrorLevel)
)

func SetStackLevel(l LogLevel) {
	stackLevel.SetLevel(logLevelToZap(l))
}

var (
	errorOutputPaths = []string{"stderr"}
)

var clock = System

type ClockType string

const (
	System ClockType = "system"
)

type (
	LogField struct {
		Key   string
		Value any
	}

	LogWriter interface {
		Close() error
		Sync() error
		Debug(msg string, fields ...LogField)
		Error(msg string, fields ...LogField)
		ErrorStack(msg string, fields ...LogField)
		Info(msg string, fields ...LogField)
		Fatal(msg string, fields ...LogField)
		Warn(msg string, fields ...LogField)
		Panic(msg string, fields ...LogField)
		DPanic(msg string, fields ...LogField)
	}
)

type ZapWriter struct {
	l       *zap.Logger
	closeFn []func()
}

var logWriter LogWriter

func (z *ZapWriter) Close() error {
	for _, fn := range z.closeFn {
		fn()
	}
	return nil
}

func (z *ZapWriter) Sync() error {
	return z.l.Sync()
}

func (z *ZapWriter) Debug(msg string, fields ...LogField) {
	z.l.Debug(msg, toZapFields(fields...)...)
}

func (z *ZapWriter) Error(msg string, fields ...LogField) {
	z.l.Error(msg, toZapFields(fields...)...)
}

// ErrorStack 添加额外的
func (z *ZapWriter) ErrorStack(msg string, fields ...LogField) {
	fs := toZapFields(fields...)
	fs = append(fs, zap.Stack("error_stack"))
	z.l.Error(msg, fs...)
}

func (z *ZapWriter) Info(msg string, fields ...LogField) {
	z.l.Info(msg, toZapFields(fields...)...)
}

func (z *ZapWriter) Fatal(msg string, fields ...LogField) {
	z.l.Fatal(msg, toZapFields(fields...)...)
}

func (z *ZapWriter) Warn(msg string, fields ...LogField) {
	z.l.Warn(msg, toZapFields(fields...)...)
}

func (z *ZapWriter) Panic(msg string, fields ...LogField) {
	z.l.Panic(msg, toZapFields(fields...)...)
}

func (z *ZapWriter) DPanic(msg string, fields ...LogField) {
	z.l.DPanic(msg, toZapFields(fields...)...)
}
func toZapFields(fields ...LogField) []zap.Field {
	zapFields := make([]zap.Field, 0, len(fields))
	for _, f := range fields {
		zapFields = append(zapFields, zap.Any(f.Key, f.Value))
	}
	return zapFields
}

func SetUp(c LogConf) error {
	applyConfig(c)
	err := setupZap()
	if err != nil {
		return err
	}
	return nil
}

func applyConfig(c LogConf) {
	isTerm = c.IsTerm
	if len(strings.TrimSpace(c.ServiceName)) > 0 {
		name = c.ServiceName
	}
	switch c.DurationStyle {
	case "second":
		durationStyle = SecondsDuration
	case "nano":
		durationStyle = NanosDuration
	case "milli":
		durationStyle = MillisDuration
	case "string":
		durationStyle = StringDuration
	default:
	}
	//iso8601,epoch,epoch_millis,epoch_nanos,rfc3339,rfc3339_nano,layout
	switch c.TimeStyle {
	case "iso8601":
		timeStyle = ISO8601
	case "epoch":
		timeStyle = EpochTime
	case "epoch_millis":
		timeStyle = EpochMillisTime
	case "epoch_nanos":
		timeStyle = EpochNanosTime
	case "rfc3339":
		timeStyle = RFC3339
	case "rfc3339_nano":
		timeStyle = RFC3339Nano
	case "layout":
		timeStyle = Layout
	default:
	}
	switch c.LevelStyle {
	case "lowercase":
		levelStyle = Lowercase
	case "capital":
		levelStyle = Capital
	case "lowercase_color":
		levelStyle = LowercaseColor
	case "capital_color":
		levelStyle = CapitalColor
	default:
	}

	switch c.CallerStyle {
	case "short":
		callerStyle = Short
	case "full":
		callerStyle = Full
	default:
	}

	switch c.LoggerNameStyle {
	case "full_name":
		loggerNameStyle = FullName
	default:
	}

	switch c.OutStyle {
	case "plain":
		outStyle = PlainText
	case "json":
		outStyle = Json
	default:
	}
	if len(strings.TrimSpace(c.TimeKey)) > 0 {
		timeKey = c.TimeKey
	}
	if len(strings.TrimSpace(c.LevelKey)) > 0 {
		levelKey = c.LevelKey
	}
	if len(strings.TrimSpace(c.NameKey)) > 0 {
		nameKey = c.NameKey
	}
	if len(strings.TrimSpace(c.CallerKey)) > 0 {
		callerKey = c.CallerKey
	}
	if len(strings.TrimSpace(c.FunctionKey)) > 0 {
		functionKey = c.FunctionKey
	}
	if len(strings.TrimSpace(c.MessageKey)) > 0 {
		messageKey = c.MessageKey
	}
	if len(strings.TrimSpace(c.LineEnding)) > 0 {
		lineEnding = c.LineEnding
	}
	if len(strings.TrimSpace(c.TimeFormat)) > 0 {
		timeFormat = c.TimeFormat
	}
	if len(strings.TrimSpace(c.StacktraceKey)) > 0 {
		stacktraceKey = c.StacktraceKey
	}
	switch c.StackLevel {
	case "debug":
		SetStackLevel(DebugLevel)
	case "info":
		SetStackLevel(InfoLevel)
	case "warn":
		SetStackLevel(WarnLevel)
	case "error":
		SetStackLevel(ErrorLevel)
	case "dpanic":
		SetStackLevel(DPanicLevel)
	case "panic":
		SetStackLevel(PanicLevel)
	case "fatal":
		SetStackLevel(FatalLevel)
	case "none":
		SetStackLevel(NoneLevel)
	default:
	}
	switch c.LogLevel {
	case "debug":
		SetLogLevel(DebugLevel)
	case "info":
		SetLogLevel(InfoLevel)
	case "warn":
		SetLogLevel(WarnLevel)
	case "error":
		SetLogLevel(ErrorLevel)
	case "dpanic":
		SetLogLevel(DPanicLevel)
	case "panic":
		SetLogLevel(PanicLevel)
	case "fatal":
		SetLogLevel(FatalLevel)
	case "none":
		SetLogLevel(NoneLevel)
	default:
	}

	var outputPathsTemp []string
	for _, p := range c.OutputPaths {
		if len(strings.TrimSpace(p)) > 0 {
			outputPathsTemp = append(outputPathsTemp, p)
		}
	}
	if len(outputPathsTemp) > 0 {
		outputPaths = outputPathsTemp
	}

	var errorOutputPathsTemp []string
	for _, p := range c.ErrorOutputPaths {
		if len(strings.TrimSpace(p)) > 0 {
			errorOutputPathsTemp = append(errorOutputPathsTemp, p)
		}
	}
	if len(errorOutputPathsTemp) > 0 {
		errorOutputPaths = errorOutputPathsTemp
	}

	addCaller = c.AddCaller

	switch c.OnFatal {
	case "goexit":
		onFatal = GoExit
	case "panic":
		onFatal = Panic
	case "exit":
		onFatal = Exit
	default:
	}

	callerSkip = c.CallerSkip

	switch c.Clock {
	case "system":
		clock = System
	default:
	}
}
func setupZap() error {
	encCfg := zapcore.EncoderConfig{
		TimeKey:       timeKey,
		LevelKey:      levelKey,
		NameKey:       nameKey,
		CallerKey:     callerKey,
		StacktraceKey: stacktraceKey,
		FunctionKey:   functionKey,
		MessageKey:    messageKey,
		LineEnding:    lineEnding,
	}
	switch durationStyle {
	case StringDuration:
		encCfg.EncodeDuration = zapcore.StringDurationEncoder
	case MillisDuration:
		encCfg.EncodeDuration = zapcore.MillisDurationEncoder
	case SecondsDuration:
		encCfg.EncodeDuration = zapcore.SecondsDurationEncoder
	case NanosDuration:
		encCfg.EncodeDuration = zapcore.NanosDurationEncoder
	}
	switch timeStyle {
	case Layout:
		encCfg.EncodeTime = zapcore.TimeEncoderOfLayout(timeFormat)
	case EpochNanosTime:
		encCfg.EncodeTime = zapcore.EpochNanosTimeEncoder
	case EpochMillisTime:
		encCfg.EncodeTime = zapcore.EpochMillisTimeEncoder
	case EpochTime:
		encCfg.EncodeTime = zapcore.EpochTimeEncoder
	case ISO8601:
		encCfg.EncodeTime = zapcore.ISO8601TimeEncoder
	case RFC3339Nano:
		encCfg.EncodeTime = zapcore.RFC3339NanoTimeEncoder
	case RFC3339:
		encCfg.EncodeTime = zapcore.RFC3339TimeEncoder
	}

	switch levelStyle {
	case Lowercase:
		encCfg.EncodeLevel = zapcore.LowercaseLevelEncoder
	case Capital:
		encCfg.EncodeLevel = zapcore.CapitalLevelEncoder
	case CapitalColor:
		encCfg.EncodeLevel = zapcore.CapitalColorLevelEncoder
	case LowercaseColor:
		encCfg.EncodeLevel = zapcore.LowercaseColorLevelEncoder
	}
	switch callerStyle {
	case Short:
		encCfg.EncodeCaller = zapcore.ShortCallerEncoder
	case Full:
		encCfg.EncodeCaller = zapcore.FullCallerEncoder
	}
	switch loggerNameStyle {
	case FullName:
		encCfg.EncodeName = zapcore.FullNameEncoder
	}

	var enc zapcore.Encoder
	switch outStyle {
	case PlainText:
		enc = zapcore.NewConsoleEncoder(encCfg)
	case Json:
		enc = zapcore.NewJSONEncoder(encCfg)
	}

	//这些预准备的文件应该在服务器关闭前，调用async之前关闭他们
	sink, closeOut, err := zap.Open(outputPaths...) //日志的输出位置，将来要设置到core
	if err != nil {
		if nil != closeOut {
			closeOut()
		}
		return err
	}

	errSink, closeOutErr, err := zap.Open(errorOutputPaths...) //log内部出错日志的输出位置，将来要设置到logger
	if err != nil {
		if nil != closeOut {
			closeOutErr()
		}
		return err
	}

	opts := []zap.Option{zap.ErrorOutput(errSink)}
	if addCaller {
		opts = append(opts, zap.AddCaller())
	}
	opts = append(opts, zap.AddCallerSkip(callerSkip))
	switch onFatal {
	case Panic:
		opts = append(opts, zap.WithFatalHook(zapcore.WriteThenPanic))
	case Exit:
		opts = append(opts, zap.WithFatalHook(zapcore.WriteThenFatal))
	case GoExit:
		opts = append(opts, zap.WithFatalHook(zapcore.WriteThenGoexit))
	}

	switch clock {
	case System:
		opts = append(opts, zap.WithClock(zapcore.DefaultClock))
	}

	opts = append(opts, zap.AddStacktrace(stackLevel))

	c := zapcore.NewCore(enc, sink, logLevel)

	log := zap.New(c, opts...).Named(name)
	var ch ZapWriter
	ch.l = log
	ch.closeFn = append(ch.closeFn, closeOut)
	logWriter = &ch
	return nil
}
func getWriter() LogWriter {
	return logWriter
}
func DebugLog(msg string, fields ...LogField) {
	getWriter().Debug(msg, fields...)
}

func ErrorLog(msg string, fields ...LogField) {
	getWriter().Error(msg, fields...)
}
func ErrorLogStack(msg string, fields ...LogField) {
	getWriter().ErrorStack(msg, fields...)
}

func InfoLog(msg string, fields ...LogField) {
	getWriter().Info(msg, fields...)
}

func FatalLog(msg string, fields ...LogField) {
	getWriter().Fatal(msg, fields...)
}

func WarnLog(msg string, fields ...LogField) {
	getWriter().Warn(msg, fields...)
}
func WarnLogF(msg string, args ...any) {
	getWriter().Warn(fmt.Sprintf(msg, args))
}

func PanicLog(msg string, fields ...LogField) {
	getWriter().Panic(msg, fields...)
}

func DPanicLog(msg string, fields ...LogField) {
	getWriter().DPanic(msg, fields...)
}
func Sync() error {
	return getWriter().Sync()
}
func Close() error {
	return getWriter().Close()
}
func IsTerm() bool {
	return isTerm
}
